#include "GLFWApp.h"
#include "Shapes3D.h"
#include "fonts/bitmap_font.h"
#include <cstdlib>
#include "basswrap/bass_wrapper.h"
#include <map>
#include <string>
#include <list>

#include <utils.hpp>
using namespace siut::simd;

#include "verlet_engine.h"

class Planetoid
{
public:
	SpringBody *body;
	bool isAlive;
	int hitPoints;
	World *world;

	Planetoid(World *world, real x, real y)
	{
		body = SpringFactory::makeSphere(world,x,y,2.0f, true);

		//body = SpringFactory::makeBox(world, x-1,y-1,2,2 );
		body->setFrozen();
		this->world = world;
		body->isAlive = true;

		isAlive = true;
		hitPoints = 50;
	}

	void update()
	{
		if( !isAlive ) return;

		if( hitPoints <= 0)
		{
			isAlive = false;
			explode();
		}
	}

	void explode()
	{
		real cx = body->centroid.x();
		real cy = body->centroid.y();
		Vec2f bodyCenter = body->centroid;
		std::vector<SpringBody*> fractureList;
		SpringFactory::makeDebris( world, body, fractureList );
		for each(SpringBody* b in fractureList)
		{
			b->calcCenter();
			b->addForce( 0.01 * normalize(b->centroid - bodyCenter) );
		}

		world->removeBody( body );		
	}
};

class Enemy
{
public:
	SpringBody *body;
	bool isAlive;
	int hitPoints;

	Enemy(World *world)
	{
		real siz = 0.3;
		real x = 30;
		real y = 30;
#ifdef _DEBUG
		assert( world->isSpaceEmpty( x-siz, y-siz, x+siz, y+siz) == true );
#endif
		body = SpringFactory::makeSphere(world, x,y,siz, false);
		body->isAlive = true;

		isAlive = true;
		hitPoints = 50;
	}
};

class MyApp : public GLFWApp
{
public:
	MyApp() : GLFWApp()
	{
		init();
		mainloop();
	}

	virtual ~MyApp()
	{
		freeData();
	}

	void freeData()
	{
		std::map<std::string,unsigned long>::iterator it;

		// show content:
		for ( it=sounds.begin() ; it != sounds.end(); it++ )
		{
			basswrap.freeStream( (*it).second );
		}
	}

	void init()
	{
		// Sound
		sounds.insert( std::pair<std::string,int>("weakhit", basswrap.getHandleAndLoad("sounds/weakhit.wav") ) );
		sounds.insert( std::pair<std::string,int>("hit", basswrap.getHandleAndLoad("sounds/hit.wav") ) );
		sounds.insert( std::pair<std::string,int>("pew", basswrap.getHandleAndLoad("sounds/pew.wav") ) );
		sounds.insert( std::pair<std::string,int>("spawn", basswrap.getHandleAndLoad("sounds/spawn.wav") ) );
		sounds.insert( std::pair<std::string,int>("explode", basswrap.getHandleAndLoad("sounds/explode.wav") ) );

		glfwSwapInterval(1);
		BitmapFont::Init();

		timeSlept = 0;

		player = SpringFactory::makeBox(&physWorld, -2.5,-2.5,0.5,0.5 );
		enemyUnit = new Enemy(&physWorld);		

		real boxSz = 0.5;
		real xmin = -5.0;
		real xmax = 5.0;
		real xpos = xmin;
		real ypos = -2;

		for(int i=0; i<0; i++)
		{
			SpringBody *box = SpringFactory::makeBox(&physWorld,xpos, ypos,boxSz,boxSz);
			xpos += boxSz*2;
			if(xpos+boxSz > xmax)
			{
				xpos = xmin;
				ypos += boxSz*1.5;
			} 
		}

		int num = 20;
		int half = num/2;
		for(int i=0; i<num; i++)
		{
			SpringBody *leftBox = SpringFactory::makeBox(&physWorld, -5.0, -half*boxSz + i * boxSz, boxSz,boxSz);
			leftBox->setFrozen();

			SpringBody *rightBox = SpringFactory::makeBox(&physWorld, 5.0, -half*boxSz + i * boxSz, boxSz,boxSz);
			rightBox->setFrozen();

			SpringBody *topBox = SpringFactory::makeBox(&physWorld, -half*boxSz + i * boxSz, 5.5, boxSz,boxSz );
			topBox->setFrozen();

			SpringBody *bottomBox = SpringFactory::makeBox(&physWorld,-half*boxSz + i * boxSz, -5.5, boxSz,boxSz );
			bottomBox->setFrozen();

		}

		SpringBody *planet = SpringFactory::makeSphere(&physWorld,0.0f,0.0f, 1.0f, true);

		for(int i=0; i<10; i++)
		{
			real xpos=0;
			real ypos=0;

			bool illegalPos = true;
			int tries = 0;
			while( illegalPos )
			{
				xpos = real(rand()) / RAND_MAX - 0.5;
				ypos = real(rand()) / RAND_MAX - 0.5;
				xpos *= 20;
				ypos *= 20;

				if( physWorld.isSpaceEmpty(xpos-2, ypos-2, xpos+2, ypos+2) )
				{
					illegalPos = false;
				}
				tries++;
				if( tries > 100 )
				{
					break;
				}
			}

			if( !illegalPos )
			{
				Planetoid *p = new Planetoid(&physWorld, xpos, ypos);
				planetoidList.push_back( p );
			}

		}

		
	}

	void updateEnemyAI()
	{
		Vec2f playerEnemyVec = player->centroid - enemyUnit->body->centroid;
		playerEnemyVec = normalize(playerEnemyVec);
		if( enemyUnit->isAlive )
		{
			enemyUnit->body->addForce( 0.01 * playerEnemyVec );
		}
	}

	void updateBullets()
	{
		Vec2f playerEnemyVec = player->centroid - enemyUnit->body->centroid;
		std::list<SpringBody*> deadBullets;

		std::list<SpringBody*>::iterator it1;
		for (it1=playerBullets.begin(); it1!=playerBullets.end(); ++it1)
		{
			SpringBody *bullet = *it1;

			if( bullet->bodiesOverlap(enemyUnit->body) )
			{
				// spawn new debris
				for(int i=0; i<1; i++)
				{
					real tx = cos( 6.28 * i / 5.0 ) * .2;
					real ty = sin( 6.28 * i / 5.0 ) * .2;
					SpringBody *triangle = SpringFactory::makeTriangle(&physWorld,enemyUnit->body->centroid.x() + tx , enemyUnit->body->centroid.y() + ty ,0.125 );
				}


				enemyUnit->hitPoints -= 5;
				enemyUnit->body->addForce( -0.1 * playerEnemyVec );
				// Enemy dies
				if( enemyUnit->hitPoints <= 0 )
				{
					enemyUnit->isAlive = false;

					for(int i=0; i<6; i++)
					{
						real tx = cos( 6.28 * i / 5.0 ) * .2;
						real ty = sin( 6.28 * i / 5.0 ) * .2;
						SpringBody *triangle = SpringFactory::makeTriangle(&physWorld,enemyUnit->body->centroid.x() + tx , enemyUnit->body->centroid.y() + ty ,0.125 );
					}

					basswrap.playStream( sounds.find("explode")->second );
				}
				deadBullets.push_back( bullet );
			}

			// Bullet vs planetoid
			std::list<Planetoid*>::iterator planetoidIterator;
			for (planetoidIterator=planetoidList.begin(); planetoidIterator!=planetoidList.end(); ++planetoidIterator)
			{
				Planetoid *planetoid = *planetoidIterator;
				if( bullet->bodiesOverlap(planetoid->body) )
				{
					planetoid->hitPoints -= 5;
					deadBullets.push_back( bullet );
				}
			}
			
		}

		std::list<SpringBody*>::iterator it2;
		for (it2=deadBullets.begin(); it2!=deadBullets.end(); ++it2)
		{
			playerBullets.remove( *it2 );
			physWorld.removeBody( (*it2) );
			
		}

	}

	
	// \breif Expects OpenGL modelview matrix to be set to world view. for instance with gluLookAt
	void updateWorldMouseCoordinates()
	{
		Vec2f mousePos( Input::mouseX()/(xres/1.0f) * 16 - 8.0f, -Input::mouseY()/(yres/1.0f) * 12 + 6 );
		Vec3f centerOfInterest( player->centroid.x(), player->centroid.y(), 0.0f );

		float mtx[16];
		glGetFloatv( GL_MODELVIEW_MATRIX, mtx );

		Mat4f mvp( mtx );
		Mat4f mvp_Inv;
		mvp_Inv = inverse(mvp);


		mousePointer = mousePosOnInterestPlaneAsObjectCoords(mvp, mvp_Inv, centerOfInterest, mousePos);
	}

	void update()
	{
		physWorld.startFrame();

		physTimeSpent = glfwGetTime();
		physWorld.update( 1 );
		physTimeSpent = glfwGetTime() - physTimeSpent;

		std::list<Planetoid*>::iterator planetoidIterator;
		for (planetoidIterator=planetoidList.begin(); planetoidIterator!=planetoidList.end(); ++planetoidIterator)
		{
			(*planetoidIterator)->update();
		}

		real vx = Input::keyDown(GLFW_KEY_RIGHT) - Input::keyDown(GLFW_KEY_LEFT);
		real vy = Input::keyDown(GLFW_KEY_UP) - Input::keyDown(GLFW_KEY_DOWN);

		vx = Input::keyDown('D') - Input::keyDown('A');
		vy = Input::keyDown('W') - Input::keyDown('S');

		if( Input::keyHit(GLFW_KEY_SPACE) )
		{
			for each(Particle *p in player->particles )
			{
				p->oldPos = p->pos;
			}
			
		}

		player->addForce( 0.01 * Vec2f(vx,vy) );


		updateEnemyAI();

		//updateBullets();
		

		if( Input::MouseHit(GLFW_MOUSE_BUTTON_LEFT) )
		{
			SpringBody *box = SpringFactory::makeBox(&physWorld,mousePointer.x()-0.25, mousePointer.y()-0.25,0.5,0.5);
			for each(Particle *p in box->particles)
			{
				p->massInverse = 1.0 / 1e6;
			}
			basswrap.playStream( sounds.find("spawn")->second );
		}

		shootDir = Vec2f(mousePointer.x(), mousePointer.y() ) - player->centroid;
		shootDir = normalize(shootDir);
		if( Input::MouseHit(GLFW_MOUSE_BUTTON_RIGHT) )
		{
			shootDir *= 0.55;
			SpringBody *box = SpringFactory::makeBox(&physWorld,player->centroid.x() + shootDir.x(), player->centroid.y() + shootDir.y(),0.15,0.15);
			box->addForce( 0.1 * shootDir );
			playerBullets.push_back( box );
			basswrap.playStream( sounds.find("pew")->second );
		}

		for(size_t i=0; i<physWorld.hardCollisions.size(); i++)
		{
			int num = physWorld.hardCollisions[i];
			switch(num)
			{
			case 1: 
				basswrap.playStream( sounds.find("weakhit")->second ); break;
			case 2: 
				basswrap.playStream( sounds.find("hit")->second ); break;
			}
			
		}

		if( Input::keyHit(GLFW_KEY_ESC) )
		{
			this->gameRunning = false;
		}

		// todo fix code of fail:
		double desiredMSPF = 0.017;

		if( dt < desiredMSPF )
		{
			timeSlept = std::max(0.0, desiredMSPF - dt);
			glfwSleep( timeSlept );
		}
	}
	
	void render()
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		setDepthProjection();
		glEnable(GL_DEPTH_TEST);
		//glDepthFunc(GL_LEQUAL);
		glLoadIdentity();

		gluLookAt( player->centroid.x(), player->centroid.y(), 16.0, 
						   player->centroid.x(), player->centroid.y(), 0.0,
							 0.0, 1.0, 0.0);

		updateWorldMouseCoordinates();

		// Get the matrices and viewport
		Mat4f modelView;
		Mat4f projection;
		float depthRange[2];

		glGetFloatv(GL_MODELVIEW_MATRIX, modelView.v_ );
		glGetFloatv(GL_PROJECTION_MATRIX, projection.v_ );
		glGetFloatv(GL_DEPTH_RANGE, depthRange );

		//Shapes3D::DrawSphere(1.0, 32, 32);

		glColor3f(1,1,1);
		physWorld.draw();

		glPushMatrix();
		glPushAttrib(GL_POLYGON_BIT);
		glTranslatef( mousePointer.x(), mousePointer.y(), mousePointer.z() );
		glColor3f(1,0,0);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glRotatef( numframes, 0,1,1 );
		Shapes3D::DrawSphere(0.25, 5, 5);
		glPopAttrib();
		glPopMatrix();

		
		glEnable(GL_LIGHTING);

		for( size_t i=0; i<1; i++)
		{
			SpringBody *body = physWorld.bodyList[i];
			glEnable(GL_LIGHT0+i);
			float pos[] = { body->centroid.x(), body->centroid.y(), 0, 1};
			glLightfv( GL_LIGHT0+i, GL_POSITION, pos );

			float dif[] = { 0.125, 0.125, 0.125, 1};
			glLightfv( GL_LIGHT0+i, GL_DIFFUSE, dif);
			
			float spc[] = { 1.0, 1.0, 1.0, 1};
			glLightfv( GL_LIGHT0+i, GL_SPECULAR, spc);

			float amb[] = { 0.0, 0.0, 0.0, 1};
			glLightfv( GL_LIGHT0+i, GL_AMBIENT, amb);

			glLightf( GL_LIGHT0+i, GL_CONSTANT_ATTENUATION, 0.0f);
			glLightf( GL_LIGHT0+i, GL_LINEAR_ATTENUATION, 0.00f);
			glLightf( GL_LIGHT0+i, GL_QUADRATIC_ATTENUATION, 0.001f);

		}

		//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glFrontFace(GL_CCW);
		
		glColor3f(1,0,0);
		glPushMatrix();
		
		// player->centroid.x(), player->centroid.y()
		glTranslatef(player->centroid.x(), player->centroid.y(), -20 );
		for( int i=-20; i<20; i++)
		{
			for( int j=-20; j < 20; j++)
			{
				glBegin(GL_QUADS);
					glNormal3f(0,0,1);
					glVertex3f(i,j,0);
					glVertex3f(i,j+1,0);
					glVertex3f(i+1,j+1,0);
					glVertex3f(i+1,j,0);
				glEnd();
			}
		}
		glPopMatrix();

		glDisable(GL_LIGHTING);
		glDisable(GL_LIGHT0);

		glDisable(GL_DEPTH_TEST);


		glPushMatrix();
		glTranslatef(player->centroid.x(), player->centroid.y(), 0);
		glBegin(GL_LINES);
		glVertex3f(0,0,0);
		glVertex3f(shootDir.x(), shootDir.y(), 0.0f);
		glEnd();
		glPopMatrix();

		setOrthoProjection();
		glLoadIdentity();

		char charBuf[200];
		sprintf_s( charBuf, 200, "fps = %f", fps );
		BitmapFont::Print(charBuf, 15,15,0xff0000);
		sprintf_s( charBuf, 200, "dt = %f", dt );
		BitmapFont::Print(charBuf, 15,15*2,0x00ff00);
		sprintf_s( charBuf, 200, "timeslept = %f", timeSlept );
		BitmapFont::Print(charBuf, 15,15*3,0x0000ff);
		sprintf_s( charBuf, 200, "physTimeSpentx1000 = %f", 1000*physTimeSpent );
		BitmapFont::Print(charBuf, 15,15*4,0x00ffff);
		sprintf_s( charBuf, 200, "num body objects: %i", physWorld.bodyList.size() );
		BitmapFont::Print(charBuf, 15,15*5, 0xff000);
		sprintf_s( charBuf, 200, "playerspeed: %f", player->particles[0]->speed );
		BitmapFont::Print(charBuf, 15,15*6, 0xffff00);	
		sprintf_s( charBuf, 200, "enemy hp: %i", enemyUnit->hitPoints );
		BitmapFont::Print(charBuf, 15,15*7, 0xff00ff);	

		sprintf_s( charBuf, 200, "Press ESC to quit!" );
		BitmapFont::Print(charBuf, 15,15*8, 0xffffff);	

		std::list<Planetoid*>::iterator planetoidIterator;
		for (planetoidIterator=planetoidList.begin(); planetoidIterator!=planetoidList.end(); ++planetoidIterator)
		{
			Planetoid *planetoid = *planetoidIterator;
		
			Vec4f pos( planetoid->body->centroid.x(), planetoid->body->centroid.y(), 0, 1.0f);
			Vec4f mvp = projection * (modelView*pos);
			Vec4f ndc = mvp / mvp.w();

			Vec4f newPos = mvp * pos;
			newPos.x() *= xres;
			newPos.y() *= yres;

			float zWindow = (depthRange[1] - depthRange[0])/2.0f * ndc.z() + (depthRange[1] + depthRange[0])/2.0f;

			if( zWindow < 1.0f )
			{
				sprintf_s( charBuf, 200, "%i", planetoid->hitPoints );
				glPushMatrix();
				glPointSize(3.0);
				float px = xres/2.0f + ndc.x() * xres/2.0f;
				float py = yres/2.0f - ndc.y() * yres/2.0f;
				glTranslatef(-2*px,-2*py,0);
				glScalef(3,3,2);
				BitmapFont::Print(charBuf,px,py,0x00ff00);	
				glPointSize(1.0);
				glPopMatrix();
			}
		}


	}

private:
	World physWorld;

	SpringBody *player;
	Enemy *enemyUnit;

	double timeSlept;
	double physTimeSpent;

	Vec3f mousePointer;
	Vec2f shootDir;

	BassWrapper basswrap;

	std::map<std::string, unsigned long> sounds;

	std::list< SpringBody* > playerBullets;
	std::list< Planetoid* > planetoidList;
	



};